using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace ru.yourpictures.Core.Imaging.Filters.PhotoFilters
{
    #region TODOs
    //TODO: 
    //1. For non rectengular images, add an edge detection algo, the drops 
    //   the shadow according to the object in the image.
    //2. Add the distance to lighting source :) (in pixels)
    #endregion TODOs

    /// <summary>
    /// DropShadow. Adds the picture a drop shadow as if the image is hovering 
    /// above the floor or infront of the wall.
    /// Reference: http://www.codeproject.com/KB/cs/yael_image_filters.aspx
    /// </summary>
    [FilterImplementation(GdiPlusImplemented = true, LowLevelImplemented = false)]
    public class DropShadow : FilterBase
    {
        public static double MinVerticalAngle = 1.0;
        public static double MaxVerticalAngle = 89.0;

        #region ShadowTypes
        public enum ShadowTypes
        {
            SingleColor,
            Gradient,
            Image
        }
        #endregion

        #region Private fields
        private ShadowTypes _shadowType = ShadowTypes.SingleColor;
        private int _gapPixels = 20;
        private double _lightingAngleProjection = 135.0;
        private double _lightingAngleVertical = 45.0;
        private System.Drawing.Color _startShadowColor = System.Drawing.Color.Empty;
        private System.Drawing.Color _endShadowColor = System.Drawing.Color.Empty;
        private System.Drawing.Color _shadowColor = System.Drawing.Color.Empty;
        private LinearGradientMode _gradientMode = LinearGradientMode.ForwardDiagonal;
        private Image _shadowImage = null;
        #endregion

        #region Public properties
        public ShadowTypes ShadowType
        {
            get { return _shadowType; }
            set { _shadowType = value; }
        }

        /// <summary>
        /// Used when ShadowType = SingleColor
        /// </summary>
        public System.Drawing.Color ShadowColor
        {
            get { return _shadowColor; }
            set { _shadowColor = value; }
        }

        /// <summary>
        /// Used to fill shadow area
        /// </summary>
        public Image ShadowImage
        {
            get { return _shadowImage; }
            set { _shadowImage = value; }
        }

        /// <summary>
        /// Gradient mode. Used when shadow type is Gradient
        /// </summary>
        public LinearGradientMode GradientMode
        {
            get { return _gradientMode; }
            set { _gradientMode = value; }
        }

        /// <summary>
        /// When shadow is drawn by gradient brush it defines end color
        /// </summary>
        public System.Drawing.Color EndShadowColor
        {
            get { return _endShadowColor; }
            set { _endShadowColor = value; }
        }

        /// <summary>
        /// When shadow is drawn by gradient brush it defines start color
        /// </summary>
        public System.Drawing.Color StartShadowColor
        {
            get { return _startShadowColor; }
            set { _startShadowColor = value; }
        }

        /// <summary>
        /// Measured in degrees (x/360).
        /// Angle between <see cref="LightingAngleProjection"/> and line
        /// from the center of picture to the lighting source. Default 45.
        /// Value must be between 1 and 89 degrees.
        /// </summary>
        public double LightingAngleVertical
        {
            get { return _lightingAngleVertical; }
            set { _lightingAngleVertical = value; }
        }

        /// <summary>
        /// Measured in degrees (x/360). Default 135 (light coming from top left corner)
        /// Draw the line between the center of picture and lighting point. For the simplicity sake
        /// lighting source is an abstracted remote point. If you connect these 2 points by line and
        /// light up it by lighting source (also abstracted and remote) you will see a shadow, this
        /// line drops to the picture.
        /// </summary>
        public double LightingAngleProjection
        {
            get { return _lightingAngleProjection; }
            set { _lightingAngleProjection = value; }
        }

        /// <summary>
        /// How many "pixels" are between the picture's canvas and the surface, shadow falls on.
        /// Default value is 20. Can not be negative!
        /// </summary>
        public int GapPixels
        {
            get { return _gapPixels; }
            set { _gapPixels = value; }
        }
        #endregion

        #region Ctor
        public DropShadow() { }
        #endregion

        #region Public Filter Methods
        public override void Apply(Bitmap b)
        {
            _b = b;
            b = Convert(b);
        }

        /// <summary>
        /// Executes this drop shadow 
        /// filter on the input image and returns the result
        /// </summary>
        /// <param name="inputImage">input image</param>
        /// <returns>Shadow Dropped Image</returns>
        /// <example>
        /// <code>
        /// Image transformed;
        /// DropShadow dropShadow = new DropShadow();
        /// transformed = dropShadow.ExecuteFilter(myImg);
        /// </code>
        /// </example>
        public override Bitmap Convert(Bitmap b)
        {
            #region Align angles
            while (_lightingAngleProjection < 0.0)
                _lightingAngleProjection += 360.0;
            while (_lightingAngleProjection >= 360.0)
                _lightingAngleProjection -= 360.0;
            while (_lightingAngleVertical < 0.0)
                _lightingAngleVertical += 90.0;
            while (_lightingAngleVertical > 90.0)
                _lightingAngleVertical -= 90.0;

            if (_lightingAngleVertical > MaxVerticalAngle) _lightingAngleVertical = MaxVerticalAngle;
            if (_lightingAngleVertical < MinVerticalAngle) _lightingAngleVertical = MinVerticalAngle;
            #endregion

            #region Shadow lines calculation
            // We assume that all our images are non transparent rectangles
            // TODO: improve this filter for non-rectangle images
            // Width of the shadow on the left
            int leftWidth = 0;
            // Height of the shadow above the picture
            int topHeight = 0;
            // Width of the shadow on the right
            int rightWidth = 0;
            // Height of the shadow below the picture
            int bottomHeight = 0;

            double radVert = 2 * Math.PI * _lightingAngleVertical / 360;
            double radProj = 2 * Math.PI * _lightingAngleProjection / 360;

            // Max length of the shadow in direction of projection
            double l = (double)_gapPixels / Math.Tan(radVert);

            // Right/left displacement
            double x = Math.Cos(radProj) * l;

            // Top/bottom displacement
            double y = Math.Sin(radProj) * l;

            // Calculate right shadow
            if (_lightingAngleProjection > 90.0 &&
                _lightingAngleProjection < 270.0)
            {
                if (x < 0)
                    rightWidth = (int)Math.Abs(x);
            }

            // Calculate left shadow
            if (_lightingAngleProjection < 90.0 ||
                _lightingAngleProjection > 270.0)
            {
                if (x > 0)
                    leftWidth = (int)x;
            }

            // Calculate bottom shadow
            if (_lightingAngleProjection > 0.0 &&
                _lightingAngleProjection < 180.0)
            {
                if (y > 0)
                    bottomHeight = (int)y;
            }

            // Calculate top shadow
            if (_lightingAngleProjection > 180.0)
            {
                if (y < 0)
                    topHeight = (int)Math.Abs(y);
            }
            #endregion

            Bitmap fullImage = new Bitmap(b.Width + rightWidth + leftWidth, b.Height + topHeight + bottomHeight);
            Graphics g = Graphics.FromImage(fullImage);
            // Set interpolation mode
            g.InterpolationMode = _interpolationMode;
            //Set background
            g.FillRectangle(new SolidBrush(BackGroundColor), 0, 0, fullImage.Width, fullImage.Height);


            // In current version - Rectangle, in future - Polygons by transparency.
            Rectangle srcRect = new Rectangle(rightWidth, bottomHeight, b.Width, b.Height);
            if (_shadowType == ShadowTypes.Gradient)
            {
                LinearGradientBrush topBrush = new LinearGradientBrush(srcRect, _startShadowColor, _endShadowColor, _gradientMode);
                g.FillRectangle(topBrush, srcRect);
                //g.FillPolygon
            }
            else if (_shadowType == ShadowTypes.SingleColor)
            {
                g.FillRectangle(new SolidBrush(_shadowColor), srcRect);
                //g.FillPolygon
            }
            else if (_shadowType == ShadowTypes.Image)
            {
                // Create horizontal and vertical shadows (rectangles)
                Rectangle hRect = new Rectangle(0, 0, b.Width, 0);
                Rectangle vRect = new Rectangle(0, 0, 0, b.Height);
                if (leftWidth == 0)
                {
                    hRect.X = rightWidth;
                    vRect.X = b.Width;
                }
                if (topHeight == 0)
                {
                    hRect.Y = b.Height;
                    vRect.Y = bottomHeight;
                }

                g.DrawImage(_shadowImage, hRect);
                g.DrawImage(_shadowImage, vRect);
            }

            g.DrawImage(b, leftWidth, topHeight, b.Width, b.Height);
            return fullImage;
        }
        #endregion

        protected override void FilterImplementation()
        {
            throw new NotImplementedException("Low level mode is not implemented");
        }
    }
}
